<?php
/**
* 实现网络唤醒功能
*/
class WOL
{
    private $hostname;    // 唤醒设备的url地址
    private $mac;         // 唤醒设备的mac地址
    private $port;        // 唤醒设备的端口
    private $ip;          // 唤醒设备的ip地址(不是必须的,程序会自动根据$hostname来获取对应的ip)
 
    private $msg = array(
        0 => "目标机器已经是唤醒的.",
        1 => "socket_create 方法执行失败",
        2 => "socket_set_option 方法执行失败",
        3 => "magic packet 发送成功!",
        4 => "magic packet 发送失败!"
    );
     
    function __construct($hostname,$mac,$port,$ip = false)
    {
        $this->hostname = $hostname;
        $this->mac      = $mac;
        $this->port     = $port;
        if (!$ip)
        {
            $this->ip   = $this->get_ip_from_hostname();
        }
        echo "hostname = ";
        echo $hostname;
        echo "; mac = ";
        echo $mac;
        echo "; port =  ";
        echo $port ;
        echo "; ip =  ";
        echo $this->ip;
        echo "<br/>";
    }
 
    /**
     * 唤起操作
     * 
     */
    public function wake_on_wan()
    {
        if ($this->is_awake())
        {
            return $this->msg[0]; // 如果设备已经是唤醒的就不做其它操作了
        }
        else
        {
            $addr_byte = explode(':', $this->mac);
            echo "addr_byte=", $addr_byte, "<br/>";
            $hw_addr = '';
            for ($a=0; $a<6; $a++){
                $hw_addr .= chr(hexdec($addr_byte[$a]));
            } 

            $msg = chr(255).chr(255).chr(255).chr(255).chr(255).chr(255);

            for ($a=1; $a<=16; $a++) {
                $msg .= $hw_addr;
            }
            // 通过 UDP 发送数据包
            $s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
            echo "socket=", $s, "<br/>";
            if ($s == false)
            {
                return $this->msg[1]; // socket_create 执行失败
            }
 
            $set_opt = @socket_set_option($s, 1, 6, TRUE);
 
            if ($set_opt < 0)
            {
                return $this->msg[2]; // socket_set_option 执行失败
            }
 
            $sendto = @socket_sendto($s, $msg, strlen($msg), 0, $this->ip, $this->port);
            
            echo "sendto=", $sendto, "<br/>";
            
            if ($sendto)
            {
                socket_close($s);
                return $this->msg[3]; // magic packet 发送成功!
            }
 
            return $this->msg[4]; // magic packet 发送失败!
             
        }
    }
 
    /**
     * 80端口是否打开
     */
    private function is_awake()
    {
        $awake = @fsockopen($this->ip, 80, $errno, $errstr, 2);
        echo $this->ip;
        echo ":80端口是否打开:";
        echo ($awake);
        echo "<br/>"; 
        if ($awake)
        {
            fclose($awake);
        }
        
        
        return $awake;
    }
 
    private function get_ip_from_hostname()
    {
        return gethostbyname($this->hostname);
    }
 
}


/**
 * 加载唤起方法
 * @param $hostname
 */
function wakeOn($ip, $mac, $port){
    if($ip && $port && $mac){
        $WOL = new WOL($ip,$mac,$port);
    	$status = $WOL->wake_on_wan();
    	echo $status;
    }else{
        echo "参数缺失";
    }

}

/**
 * 唤起方法
 * @param $addr 唤起ip地址
 * @param $mac 唤起mac
 * @param $socket_number 端口号
 * @return bool 是否唤起唱歌
 */
function WakeOnLan($addr, $mac, $socket_number)
{
    echo "$addr", ":", "$socket_number","\n";
    $awake = @fsockopen($addr, 80, $errno, $errstr, 2);
    if ($awake)
    {
        fclose($awake);
        echo "目标机器80端口已经是唤醒的！";
        return FALSE;
    }
    if($socket_number == ""){
        echo "遍历发送所有端口!\n";
        $addr_byte = explode(':', $mac);
        $hw_addr = '';
        for ($a = 0; $a < 6; $a++) {
            $hw_addr .= chr(hexdec($addr_byte[$a]));
        }
        $msg = chr(255) . chr(255) . chr(255) . chr(255) . chr(255) . chr(255);
        for ($a = 1; $a <= 16; $a++) 
        {
            $msg .= $hw_addr;
        }
        // 使用UDP socket傳播消息
        $s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        if ($s == false) {
            echo "创建socket错误!\n";
            echo "错误代码是：'" . socket_last_error($s) . "' - " . socket_strerror(socket_last_error($s));
            return FALSE;
        } else {
            //設置socket的選項
            $opt_ret = socket_set_option($s, SOL_SOCKET, SO_REUSEADDR, 1);
            if ($opt_ret < 0) {
                echo "设置socket选项失败, 错误是: " . strerror($opt_ret) . "\n";
                return FALSE;
            }
            for ($i=1; $i<=65534; $i++)
            {
                if (socket_sendto($s, $msg, strlen($msg), 0, $addr, $i)) {
                    // socket_close($s);
                    // echo "唤起(", $i ,")", "$mac", "魔术包发送成功！";
                    // return TRUE;
                } else {
                    // echo "唤起魔术包发送失败！";
                    // return FALSE;
                }
            }
            echo "唤起", "$mac", "魔术包发送成功！";
            socket_close($s);
            return TRUE;
    
        }
    }else{
        $awake = @fsockopen($addr, $socket_number, $errno, $errstr, 2);
        if($awake){
            fclose($awake);
            echo "目标机器",$socket_number,"端口已经是唤醒的！";
            return FALSE;
        }
        $addr_byte = explode(':', $mac);
        $hw_addr = '';
        for ($a = 0; $a < 6; $a++) $hw_addr .= chr(hexdec($addr_byte[$a]));
        $msg = chr(255) . chr(255) . chr(255) . chr(255) . chr(255) . chr(255);
        for ($a = 1; $a <= 16; $a++) $msg .= $hw_addr;
        // 使用UDP socket傳播消息
        $s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        if ($s == false) {
            echo "创建socket错误!\n";
            echo "错误代码是：'" . socket_last_error($s) . "' - " . socket_strerror(socket_last_error($s));
            return FALSE;
        } else {
            //設置socket的選項
            $opt_ret = socket_set_option($s, SOL_SOCKET, SO_REUSEADDR, 1);
            if ($opt_ret < 0) {
                echo "设置socket选项失败, 错误是: " . strerror($opt_ret) . "\n";
                return FALSE;
            }
            if (socket_sendto($s, $msg, strlen($msg), 0, $addr, $socket_number)) {
                socket_close($s);
                echo "唤起", "$mac", "魔术包发送成功！";
                return TRUE;
            } else {
                echo "唤起魔术包发送失败！";
                return FALSE;
            }
    
        }
    }
    
}
    
/**
 * 唤起方法
 * @param $addr 唤起ip地址
 * @param $mac 唤起mac
 * @param $socket_number 端口号
 * @return bool 是否唤起唱歌
 */
function checkAndEtherWake($addr, $mac)
{
    $awake = @fsockopen($addr, 80, $errno, $errstr, 2);
    if ($awake)
    {
        fclose($awake);
        echo "目标机器80端口已经是唤醒的！";
        return FALSE;
    }
    
}    

/**
 * 唤起方法
 * @param $addr 唤起ip地址
 * @param $mac 唤起mac
 * @param $socket_number 端口号
 * @return bool 是否唤起唱歌
 */
function etherWake($mac)
{

    $shell = "etherwake -b -D -i br0 \"" .  $mac . "\" 2>&1 ";
    echo $shell, "\n";
    $out = shell_exec($shell);
    var_dump($out);
    return TRUE;
}  

/**
 * 发送关机报文
 */
function powerOff($ip,$port){
    $msg = "server2019poweroff";
    return send_tcp_message($ip,$port,$msg);
}

/**
 * 发送tcp消息
 */
function send_tcp_message($host, $port, $message)
{
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    
    @socket_connect($socket, $host, $port);
 
    $num = 0;
    $length = strlen($message);
    do
    {
        $buffer = substr($message, $num);
        $ret = @socket_write($socket, $buffer);
        $num += $ret;
    } while ($num < $length);
 
    // $ret = '';
    // do
    // {
    //     $buffer = @socket_read($socket, 1024, PHP_BINARY_READ);
    //     $ret .= $buffer;
    // } while (strlen($buffer) == 1024);
 
    socket_close($socket);
 
    return "发送关机指令完成";
}
  
if($_REQUEST ["action"] == "on"){
    if($_REQUEST ["ip"]){
        //从请求URL地址中获取 method 参数
        return WakeOnLan($_REQUEST ["ip"],$_REQUEST ["mac"], $_REQUEST ["port"]);
    }
    if($_REQUEST ["mac"]){
        //从请求URL地址中获取 method 参数
        return etherWake($_REQUEST ["mac"]);
    }
    if($_REQUEST ["auto"] == "wn"){
        echo $_REQUEST ["auto"];
        echo "<br/>";
        wakeOn("192.168.123.224", "00:E0:B4:1D:2A:F4", "4343");
    }
    if($_REQUEST ["auto"] == "server2019"){
        echo $_REQUEST ["auto"];
        echo "<br/>";
        wakeOn("server2019", "00:D8:61:BC:D1:9B", "4343");
    }
}

if($_REQUEST ["action"] == "off"){
    //从请求URL地址中获取 method 参数
    echo powerOff($_REQUEST ["ip"], $_REQUEST ["port"]);
}

?>